/**
  * @(#)Parameters.java
  *
  * Consolidate the application command line parameters.
  *
  * @author Course Scheduler Team
  * 
  * @license GNU General Public License version 3 (GPLv3)
  *
  * This file is part of Course Scheduler, an open source, cross platform
  * course scheduling tool, configurable for most universities.
  *
  * Copyright (C) 2010-2012 Course Scheduler Team
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation, either version 3 of the License, or
  * (at your option) any later version.
  *
  * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  * 
  */
package com.pollicitus.scheduler.core;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


/**
 * Consolidate the application command line parameters used
 * during application invocation. Command line switches
 * should be of the form --{flag}={value} for key value flags 
 * or --{flag} for on/off fags. The flag value may contain '='.
 * All other parameters will be stored in the order that they 
 * are present in the argument list.
 * 
 * @author Course Scheduler Team
 *
 */
public class Parameters {

	/**
	 * The property name for specifying that the application should print out its help text and exit.
	 * Standard fare for command line applications.
	 * <p>
	 * Value: {@value}
	 */
	public static final String HELP = "help";
	
	/**
	 * The property name for specifying that the application should print out its version information and exit.
	 * Again, standard fare for any decent application.
	 * <p>
	 * Value: {@value}
	 */
	public static final String VERSION = "version";
	
	/**
	 * The property name for specifying that the application should extract the default resources from the resource
	 * jars regardless of whether the application detects it as necessary.
	 * <p>
	 * Value:{@value}
	 */
	public static final String EXTRACT = "extract-resources";
	
	/**
	 * The property name for specifying the base configuration file. The base configuration file 
	 * is used to supply default property values to every namespace to ensure that a value is provided.
	 * <p>
	 * Value: {@value}
	 */
	public static final String BASE_CONFIG_FILE = "base.config";
	
	/**
	 * The property name for specifying the primary configuration file. The primary configuration file
	 * determines the basic application configurations including which property namespaces to load and
	 * what file to use as the source for each namespace.
	 * <p>
	 * Value: {@value}
	 */
	public static final String PRIMARY_CONFIG_FILE = "primary.config";
	
	/**
	 * The property name for specifying the root configuration directory. The root configuration directory
	 * is used as the parent directory for all relative configuration file accesses. Absolute configuration file
	 * references are not impacted by this property.
	 * <p>
	 * Value: {@value}
	 */
	public static final String CONFIG_DIRECTORY = "config.directory";
	
	/**
	 * The string that will precede the flag name when calling the application from the command line. 
	 * This should be the standard *nix long parameter format. This prefix is automatically removed
	 * before the flag is stored as a property in the Parameters class.
	 * <p>
	 * Value: {@value}
	 */
	protected static final String FLAG_PREFIX = "--";
	
	/**
	 * The string that will separate the flag name from the flag value when specifying flags from
	 * the command line. This follows typical Java syntax for assigning flag values and neatly 
	 * matches property assignment.
	 * <p>
	 * Value: {@value}
	 */
	protected static final String FLAG_SEPARATOR = "=";
	
	/**
	 * The position of the flag name in the String array resulting from splitting the flag 
	 * name from its value via the FLAG_SEPARATOR string. Canonically, the name is the first
	 * portion of the flag specification.
	 * <p>
	 * Value: {@value}
	 */
	protected static final int NAME_POSITION = 0;
	
	/**
	 * The position of the flag value in the String array resulting from splitting the flag
	 * name from its value via the FLAG_SEPARATOR string. Canonically, the value is the second
	 * portion of the flag specification.
	 * <p>
	 * Value: {@value}
	 */
	protected static final int VALUE_POSITION = 1;
	
	/**
	 * Usage string describing the valid command line flags and parameters. This string should
	 * properly reflect the runtime requirements of the application. This string is built 
	 * during instance creation.
	 */
	protected final String usage;
	
	/**
	 * A @link Map containing the flags and corresponding values that were included when the
	 * application was invoked. This Map is only modifiable while the application is parsing
	 * the input flags and arguments. Once the initialization process is complete, the flags
	 * map is set as unmodifiable. 
	 */
	private final Map<String, String> flags;
	
	
	/**
	 * A @link List containing the parameters to the application included when the application 
	 * was invoked. The parameters in this list are in the same order as the order specified
	 * on the command line or through the shell. This List is only modifiable while the 
	 * application is parsing the input flags and arguments. Once the initialization process
	 * is complete, the parms list is set as unmodifiable.
	 */
	private final List<String> parms;
	
	/**
	 * Build a new Parameters instance using the command line arguments.
	 * This is called by the application entry point to parse the arguments
	 * during initialization.
	 * 
	 * @param args the application command line arguments
	 * @return a new Parameters instance representing the command line arguments
	 */
	public static Parameters getInstance(String[] args){
		return new Parameters(args);
	}
	
	/**
	 * Create a new Parameters instance using the specified string[] 
	 * as the command line arguments. Command line arguments should be
	 * of the form -{flag}={value}
	 * 
	 * @param args the command line arguments passed to the application
	 * at invocation and collected by the Entry.main method.
	 * 
	 * @see Entry#main(String[])
	 */
	private Parameters(String[] args){
		//Temporary structures for building the modifiable collections
		Map<String, String> tempFlags = new HashMap<String, String>();
		List<String> tempParms = new ArrayList<String>();
		
		//build the usage string for error reporting
		usage = buildUsage();
		
		//initialize the flags map with default values
		buildDefaultFlags(tempFlags);
		
		//parse the flags and arguments
		parseArgs(tempParms, tempFlags, args);
		
		//make the parameter items unmodifiable since they should no longer
		//be changed by application code
		flags = Collections.unmodifiableMap(tempFlags);
		parms = Collections.unmodifiableList(tempParms);
	}
	
	/**
	 * Build the usage string for displaying the output of the 
	 * 
	 * @param args
	 * @return
	 */
	private String buildUsage(){
		//get the class which owns the invoked main
		StackTraceElement[] stack = Thread.currentThread ().getStackTrace ();
	    StackTraceElement main = stack[stack.length - 1];
	    String mainClass = main.getClassName();
				
		//build the usage string
		return new String(
			"Invoked: " + mainClass + "\n\n" +
			"usage: [optional flags] [optional args] \n" +
			" \n" +
			"	All flags and arguments (including unrecognized or unknown values) are stored in the application Settings " +
			"		class for retrieval by any application component under the '" + Settings.PARAMETER_NAMESPACE + "' namespace.\n" +
			" \n" +
			"	Optional Flags\n" +
			"		[" + FLAG_PREFIX + CONFIG_DIRECTORY + "=dir]\t\tDirectory containing application config files\n" +
			"		[" + FLAG_PREFIX + BASE_CONFIG_FILE + "=file]\t\tPath of the base config file containing default properties\n" +
			"		[" + FLAG_PREFIX + PRIMARY_CONFIG_FILE + "=file]\t\tPath of the primary config file containing the priamry configuration\n" +
			"		[" + FLAG_PREFIX + HELP + "]\t\t\tPrint out this help text and exit\n" +
			"		[" + FLAG_PREFIX + VERSION + "]\t\t\tPrint out the version information and exit\n"+
			"		[" + FLAG_PREFIX + EXTRACT + "]\t\tExtract the default application resources\n" +
			
			"	Optional Args\n" +
			"		[0			Ordered application argument, named 0\n" +
			"		[1			Ordered application argument, named 1\n" +
			"		[...]]]		Ordered application argument, named 'n'\n" +
			""			
		);		
	}
	
	/**
	 * Builds the default flag values for this Parameters instance
	 * 
	 * @param modifiable a modifiable map that will be used to build the flags map prior to 
	 * 			setting the final flags map with an unmodifiable collection
	 */
	private void buildDefaultFlags(Map<String, String> modifiable){		
		modifiable.put(BASE_CONFIG_FILE, Settings.DEFAULT_BASE_CONFIG);
		modifiable.put(PRIMARY_CONFIG_FILE, Settings.DEFAULT_PRIMARY_CONFIG);
		modifiable.put(CONFIG_DIRECTORY, Settings.DEFAULT_CONFIG_DIRECTORY);
		modifiable.put(HELP, Boolean.FALSE.toString());
		modifiable.put(VERSION, Boolean.FALSE.toString());
		modifiable.put(EXTRACT, Boolean.FALSE.toString());
	}
	
	/**
	 * Parse the command line arguments in order to override the
	 * default flags and specify application parameters
	 * 
	 * @param modifiableParms a modifiable list which will be used to build the unmodifiable parms list
	 * @param modifiableFlags a modifiable map which will be used to build the unmodifiable flags map
	 * @param args the command line arguments passed to the application
	 * at invocation and collected by the Entry.main method.
	 * @throws IllegalArgumentException If there is a problem with the flags or parameters
	 */
	private void parseArgs(List<String> modifiableParms, Map<String, String> modifiableFlags, String[] args) throws IllegalArgumentException{
		//for each command line parameter, if it matches the flag format
		//add it to the flag map. Otherwise it is a parameter
		for(String arg: args){
			if(arg.startsWith(FLAG_PREFIX)){
				//remove the flag prefix from the arg
				arg = arg.substring(FLAG_PREFIX.length());
				
				//split the flag based on the separator and add to the flags
				String[] keyValuePair = arg.split(FLAG_SEPARATOR);
				
				if(keyValuePair.length == 2){
					//Store the flag and value
					modifiableFlags.put(
						keyValuePair[NAME_POSITION],
						keyValuePair[VALUE_POSITION]
					);
				}else if(keyValuePair.length == 1){
					//Store the flag, value is TRUE (since flag is on)
					modifiableFlags.put(
						keyValuePair[NAME_POSITION],
						Boolean.TRUE.toString()
					);
				}else if(keyValuePair.length > 2){
					//Store the flag and value when the value string contains the FLAG_SEPARATOR
					String value = arg.substring(arg.indexOf(FLAG_SEPARATOR) + 1);
					modifiableFlags.put(
							keyValuePair[NAME_POSITION],
							value
						);
				} //ignore missing keys
			}else{
				//add it to the parameter list
				modifiableParms.add(arg);
			}
		}
	}
	
	/**
	 * Return the value string corresponding to the specified
	 * flag name. 
	 * 
	 * @param flagName the name of the flag
	 * @return the flag value specified at application launch
	 */
	public String getFlagValue(String flagName){
		return flags.get(flagName);
	}
	
	/**
	 * Return the array of non-flag parameters passed to the 
	 * application on the command line. 
	 * 
	 * @return array of non-flag parameters
	 */
	public String[] getParameters(){
		return parms.toArray(new String[parms.size()]);
	}
	
	/**
	 * Retrieve the non-flag parameter corresponding to the specified
	 * parameter position. 
	 * 
	 * @param pos the position of the parameter to retrieve
	 * @return the parameter value as a string
	 */
	public String getParameters(int pos){
		try{
			return parms.get(pos);
		}
		catch(Exception IndexOutOfBoundsException){
			return null;
		}
	}
}
